1use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
26use crate::copp::copp2::formulation::Copp2Problem;
27use crate::copp::copp2::opt2::ClarabelExpertInfor2nd;
28use crate::copp::copp2::opt2::clarabel_constraints::{
29 clarabel_standard_capacity_topp2, clarabel_standard_constraint_topp2,
30};
31use crate::copp::{ClarabelOptions, CoppObjective, clarabel_to_copp2_solution};
32use crate::diag::{
33 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
34 format_duration_human,
35};
36use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
37use clarabel::algebra::CscMatrix;
38use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT};
39use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
40use core::f64;
41use itertools::{Itertools, izip};
42use nalgebra::{DMatrix, DVectorView};
43
44use crate::copp::copp2::stable::basic::a_to_b_topp2;
45
46pub fn copp2_socp<'a, M: RobotTorque>(
67 problem: &Copp2Problem<'a, M>,
68 options: &ClarabelOptions,
69) -> Result<Vec<f64>, CoppError> {
70 let (a_profile, solution) = copp2_socp_expert(problem, options)?;
71 a_profile.ok_or_else(|| CoppError::ClarabelSolverStatus("copp2_socp".into(), solution.status))
72}
73
74pub fn copp2_socp_expert<'a, M: RobotTorque>(
103 problem: &Copp2Problem<'a, M>,
104 options: &ClarabelOptions,
105) -> Result<(Option<Vec<f64>>, DefaultSolution<f64>), CoppError> {
106 let result = copp2_socp_expert_with_info(problem, options)?;
107 Ok((result.result, result.solution))
108}
109
110pub fn copp2_socp_expert_with_info<'a, M: RobotTorque>(
116 problem: &Copp2Problem<'a, M>,
117 options: &ClarabelOptions,
118) -> Result<ClarabelExpertInfor2nd, CoppError> {
119 match options.verbosity() {
120 Verbosity::Silent => copp2_socp_core(problem, (options, SilentVerboser)),
121 Verbosity::Summary => copp2_socp_core(problem, (options, SummaryVerboser::new())),
122 Verbosity::Debug => copp2_socp_core(problem, (options, DebugVerboser::new())),
123 Verbosity::Trace => copp2_socp_core(problem, (options, TraceVerboser::new())),
124 }
125}
126
127fn copp2_socp_core<'a, M: RobotTorque>(
139 problem: &Copp2Problem<'a, M>,
140 options_verboser: (&ClarabelOptions, impl Verboser),
141) -> Result<ClarabelExpertInfor2nd, CoppError> {
142 let (options, mut verboser) = options_verboser;
143 let (idx_s_start, idx_s_final) = problem.idx_s_interval;
144 if verboser.is_enabled(Verbosity::Summary) {
145 verboser.record_start_time();
146 crate::verbosity_log!(
147 crate::diag::Verbosity::Summary,
148 "\ncopp2_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
149 idx_s_start,
150 idx_s_final,
151 problem.objectives.len(),
152 problem.s_len()
153 );
154 }
155 if verboser.is_enabled(Verbosity::Trace) {
156 let settings = options.clarabel_settings();
157 crate::verbosity_log!(
158 crate::diag::Verbosity::Summary,
159 "copp2_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
160 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
161 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
162 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
163 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
164 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
165 settings.tol_gap_rel,
166 settings.tol_feas,
167 settings.max_iter,
168 settings.verbose
169 );
170 }
171 let n = idx_s_final - idx_s_start;
173 let (cap_val_std, cap_b_std, cap_cone_std) =
177 clarabel_standard_capacity_topp2(&problem.robot.constraints, problem.idx_s_interval);
178 let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
179 clarabel_objective_capacity_copp2(n, problem.objectives, problem.robot);
180 if verboser.is_enabled(Verbosity::Debug) {
181 crate::verbosity_log!(
182 crate::diag::Verbosity::Summary,
183 "copp2_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
184 );
185 }
186 let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
189 let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
190 let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
191 let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
192 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
193 if verboser.is_enabled(Verbosity::Trace) {
194 crate::verbosity_log!(
195 crate::diag::Verbosity::Summary,
196 "copp2_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
197 cap_val_std + cap_val_obj,
198 cap_val_std + cap_val_obj,
199 cap_val_std + cap_val_obj,
200 cap_b_std + cap_b_obj,
201 cap_cone_std + cap_cone_obj
202 );
203 }
204 let row_before_std = row.len();
206 let col_before_std = col.len();
207 let val_before_std = val.len();
208 let b_before_std = b.len();
209 let cones_before_std = cones.len();
210 clarabel_standard_constraint_topp2(
211 &problem.as_topp2_problem(),
212 (&mut row, &mut col, &mut val, &mut b, &mut cones),
213 &verboser,
214 );
215 if verboser.is_enabled(Verbosity::Trace) {
216 crate::verbosity_log!(
217 crate::diag::Verbosity::Summary,
218 "copp2_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
219 row.len() - row_before_std,
220 col.len() - col_before_std,
221 val.len() - val_before_std,
222 b.len() - b_before_std,
223 cones.len() - cones_before_std
224 );
225 }
226 let row_before_sqrt = row.len();
229 let col_before_sqrt = col.len();
230 let val_before_sqrt = val.len();
231 let b_before_sqrt = b.len();
232 let cones_before_sqrt = cones.len();
233 let n_var_old = clarabel_sqrt_a_copp2(
234 n,
235 problem.objectives,
236 (&mut row, &mut col, &mut val, &mut b, &mut cones),
237 );
238 if verboser.is_enabled(Verbosity::Trace) {
239 crate::verbosity_log!(
240 crate::diag::Verbosity::Summary,
241 "copp2_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
242 row.len() - row_before_sqrt,
243 col.len() - col_before_sqrt,
244 val.len() - val_before_sqrt,
245 b.len() - b_before_sqrt,
246 cones.len() - cones_before_sqrt,
247 n_var_old
248 );
249 }
250 let mut q_object = Vec::<f64>::with_capacity(n_vars);
251 q_object.resize(n_var_old, 0.0);
252 let row_before_obj = row.len();
254 let col_before_obj = col.len();
255 let val_before_obj = val.len();
256 let b_before_obj = b.len();
257 let cones_before_obj = cones.len();
258 let q_before_obj = q_object.len();
259 clarable_objective_copp2(
260 problem,
261 (
262 &mut row,
263 &mut col,
264 &mut val,
265 &mut b,
266 &mut cones,
267 &mut q_object,
268 ),
269 )?;
270 if verboser.is_enabled(Verbosity::Trace) {
271 let (q_min, q_max) = q_object
272 .iter()
273 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
274 (mn.min(v), mx.max(v))
275 });
276 crate::verbosity_log!(
277 crate::diag::Verbosity::Summary,
278 "copp2_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
279 row.len() - row_before_obj,
280 col.len() - col_before_obj,
281 val.len() - val_before_obj,
282 b.len() - b_before_obj,
283 cones.len() - cones_before_obj,
284 q_object.len() - q_before_obj,
285 q_min,
286 q_max
287 );
288 }
289 if verboser.is_enabled(Verbosity::Debug) {
290 crate::verbosity_log!(
291 crate::diag::Verbosity::Summary,
292 "copp2_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
293 row.len(),
294 col.len(),
295 val.len(),
296 b.len(),
297 cones.len(),
298 q_object.len()
299 );
300 }
301 if verboser.is_enabled(Verbosity::Summary) {
302 crate::verbosity_log!(
303 crate::diag::Verbosity::Summary,
304 "copp2_socp: ready to solve with row/col/val/b/cones = {}/{}/{}/{}/{} and n_var = {}.",
305 row.len(),
306 col.len(),
307 val.len(),
308 b.len(),
309 cones.len(),
310 q_object.len()
311 );
312 }
313 let n_var = q_object.len();
315 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
316 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
317 if verboser.is_enabled(Verbosity::Trace) {
318 crate::verbosity_log!(
319 crate::diag::Verbosity::Summary,
320 "copp2_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
321 b.len(),
322 n_var,
323 a_csc.nnz(),
324 p_object.nnz()
325 );
326 }
327 let settings = options.clarabel_settings().clone();
329 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
330 .map_err(|e| CoppError::ClarabelSolverError("copp2_socp".into(), e))?;
331 solver.solve();
332 let linsolver = solver.info.linsolver.clone();
333 let solution = solver.solution;
334 if verboser.is_enabled(Verbosity::Summary) {
335 crate::verbosity_log!(
336 crate::diag::Verbosity::Summary,
337 "copp2_socp: solve done, status = {:?}, elapsed = {}.",
338 solution.status,
339 format_duration_human(verboser.elapsed())
340 );
341 }
342 if verboser.is_enabled(Verbosity::Trace) {
343 let show = solution.x.len().min(3);
344 crate::verbosity_log!(
345 crate::diag::Verbosity::Summary,
346 "copp2_socp: solution x_len={}, head={:?}",
347 solution.x.len(),
348 &solution.x[0..show]
349 );
350 }
351 let a_profile = if options.is_allow(solution.status) {
352 Some(clarabel_to_copp2_solution(problem.s_len(), &solution))
353 } else {
354 None
355 };
356 if verboser.is_enabled(Verbosity::Trace) {
357 crate::verbosity_log!(
358 crate::diag::Verbosity::Summary,
359 "copp2_socp: allow(status)={}, extracted_profile={}",
360 options.is_allow(solution.status),
361 if a_profile.is_some() {
362 "Some(a)"
363 } else {
364 "None"
365 }
366 );
367 }
368 Ok(ClarabelExpertInfor2nd {
369 result: a_profile,
370 solution,
371 linsolver,
372 })
373}
374
375fn clarabel_objective_capacity_copp2<M: RobotBasic>(
377 n: usize,
378 objective: &[CoppObjective],
379 robot: &Robot<M>,
380) -> (usize, usize, usize, usize) {
381 let flag_need_eta = objective.iter().any(|obj| {
382 matches!(
383 obj,
384 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
385 )
386 });
387 let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) = if flag_need_eta {
390 (4 * (n + 1), 4 * (n + 1), n + 2, 2 * (n + 1))
391 } else {
392 (0, 0, 0, n + 1)
393 };
394 let dim = robot.dim();
396 for obj in objective {
397 match obj {
398 CoppObjective::Time(_) => {
399 capacity_val += 6 * n;
401 capacity_b += 3 * n;
402 capacity_cones += n;
403 n_vars += n;
404 }
405 CoppObjective::ThermalEnergy(_, _) => {
406 capacity_val += (6 + 2 * dim) * n;
408 capacity_b += (dim + 2) * n;
409 capacity_cones += n;
410 n_vars += n;
411 }
412 CoppObjective::TotalVariationTorque(_, _) => {
413 capacity_val += 8 * dim * n;
415 capacity_b += 2 * dim * n;
416 capacity_cones += 1;
417 n_vars += dim * n;
418 }
419 _ => {}
420 }
421 }
422 (capacity_val, capacity_b, capacity_cones, n_vars)
423}
424
425fn clarabel_sqrt_a_copp2(
431 n: usize,
432 objective: &[CoppObjective],
433 constraints: ConstraintsClarabel,
434) -> usize {
435 let (row, col, val, b, cones) = constraints;
436 for obj in objective {
437 match obj {
438 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
439 row.extend(b.len()..b.len() + n + 1);
442 col.extend((n + 1)..(2 * (n + 1)));
443 val.resize(val.len() + n + 1, -1.0);
444 b.resize(b.len() + n + 1, 0.0);
445 cones.push(NonnegativeConeT(n + 1));
446 row.extend(b.len()..b.len() + 3 * (n + 1));
451 val.resize(val.len() + 3 * (n + 1), -1.0);
452 cones.resize(cones.len() + n + 1, SecondOrderConeT(3));
453 for k in 0..=n {
454 col.extend([k, k, k + n + 1]);
455 b.extend([0.25, -0.25, 0.0]);
456 }
457 return 2 * (n + 1);
458 }
459 _ => {}
460 }
461 }
462 n + 1
463}
464
465fn clarabel_objective_time_copp2(
468 s: &[f64],
469 weight: f64,
470 objective_constraints: ObjConsClarabel,
471) -> bool {
472 if weight < 0.0 {
473 return false;
474 }
475 let (row, col, val, b, cones, q_object) = objective_constraints;
476 let weight = 8.0 * weight;
480 let n_var_old = q_object.len();
481 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
483 let len = s.len();
487 for k in 0..(len - 1) {
488 row.resize(row.len() + 3, b.len());
490 col.extend([len + k, len + k + 1, n_var_old + k]);
491 val.extend([-1.0, -1.0, -1.0]);
492 b.push(0.0);
493 row.resize(row.len() + 3, b.len());
495 col.extend([len + k, len + k + 1, n_var_old + k]);
496 val.extend([-1.0, -1.0, 1.0]);
497 b.push(0.0);
498 b.push(1.0);
500 }
501 cones.resize(cones.len() + len - 1, SecondOrderConeT(3));
502 true
503}
504
505fn clarabel_objective_thermal_energy_copp2(
508 s: &[f64],
509 weight: f64,
510 normalize: &[f64],
511 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
512 objective_constraints: ObjConsClarabel,
513) -> bool {
514 if weight < 0.0 {
515 return false;
516 }
517 let (row, col, val, b, cones, q_object) = objective_constraints;
518 let len = s.len();
522 let mut coeff_a_curr = coeffs_torque.0.clone();
523 let mut coeff_a_next = coeffs_torque.1.clone();
524 let mut coeff_g = coeffs_torque.2.clone();
525 let weight = 8.0 * weight;
527 let n_var_old = q_object.len();
528 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
530 let dim = coeff_a_curr.nrows();
532 if normalize.len() != dim {
534 return false;
535 }
536 let normalize = DVectorView::from_slice(normalize, dim);
537 for mut col in coeff_a_curr.column_iter_mut() {
538 col.component_mul_assign(&normalize);
539 }
540 for mut col in coeff_a_next.column_iter_mut() {
541 col.component_mul_assign(&normalize);
542 }
543 for mut col in coeff_g.column_iter_mut() {
544 col.component_mul_assign(&normalize);
545 }
546 for (k, (col_a_curr, col_a_next, col_g)) in izip!(
551 coeff_a_curr.column_iter(),
552 coeff_a_next.column_iter(),
553 coeff_g.column_iter()
554 )
555 .enumerate()
556 {
557 row.resize(row.len() + 3, b.len());
559 col.extend([len + k, len + k + 1, n_var_old + k]);
560 val.extend([-1.0, -1.0, -1.0]);
561 b.push(0.0);
562 row.resize(row.len() + 3, b.len());
564 col.extend([len + k, len + k + 1, n_var_old + k]);
565 val.extend([-1.0, -1.0, 1.0]);
566 b.push(0.0);
567 for (&v_a_curr, &v_a_next, &v_g) in
569 izip!(col_a_curr.iter(), col_a_next.iter(), col_g.iter())
570 {
571 row.resize(row.len() + 2, b.len());
572 col.extend([k, k + 1]);
573 val.extend([v_a_curr, v_a_next]);
574 b.push(v_g);
575 }
576 }
577 cones.resize(cones.len() + len - 1, SecondOrderConeT(dim + 2));
578 true
579}
580
581fn clarabel_objective_tv_torque_copp2(
584 weight: f64,
585 normalize: &[f64],
586 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
587 objective_constraints: ObjConsClarabel,
588) -> bool {
589 if weight < 0.0 {
590 return false;
591 }
592 let (row, col, val, b, cones, q_object) = objective_constraints;
593 let mut coeff_a_curr = coeffs_torque.0.clone();
596 let mut coeff_a_next = coeffs_torque.1.clone();
597 let mut coeff_g = coeffs_torque.2.clone();
598 let dim = coeff_a_curr.nrows();
599 if normalize.len() != dim {
600 return false;
601 }
602 let normalize = DVectorView::from_slice(normalize, dim);
604 for mut col in coeff_a_curr.column_iter_mut() {
605 col.component_mul_assign(&normalize);
606 }
607 for mut col in coeff_a_next.column_iter_mut() {
608 col.component_mul_assign(&normalize);
609 }
610 for mut col in coeff_g.column_iter_mut() {
611 col.component_mul_assign(&normalize);
612 }
613 let n_b_old = b.len();
618 let mut buffer0 = vec![0.0; dim];
621 let mut buffer1 = vec![0.0; dim];
622 for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
623 coeff_a_curr.column_iter(),
624 coeff_a_next.column_iter(),
625 coeff_g.column_iter()
626 )
627 .tuple_windows()
628 .enumerate()
629 {
630 buffer0.clear();
632 buffer1.clear();
633 buffer0.extend(
634 col_a_next
635 .iter()
636 .zip(col_b_curr.iter())
637 .map(|(&v_a_next, &v_b_curr)| v_b_curr - v_a_next),
638 );
639 buffer1.extend(
640 col_g_curr
641 .iter()
642 .zip(col_g_next.iter())
643 .map(|(&v_g_curr, &v_g_next)| v_g_curr - v_g_next),
644 );
645 let n_var_old = q_object.len();
648 for (i, (&v0, &v1, &v_a_curr, &v_b_next)) in izip!(
649 buffer0.iter(),
650 buffer1.iter(),
651 col_a_curr.iter(),
652 col_b_next.iter()
653 )
654 .enumerate()
655 {
656 row.resize(row.len() + 4, b.len());
659 col.extend([k, k + 1, k + 2, n_var_old + i]);
660 val.extend([-v_a_curr, v0, v_b_next, -1.0]);
661 b.push(v1);
662
663 row.resize(row.len() + 4, b.len());
666 col.extend([k, k + 1, k + 2, n_var_old + i]);
667 val.extend([v_a_curr, -v0, -v_b_next, -1.0]);
668 b.push(-v1);
669 }
670
671 q_object.resize(q_object.len() + dim, weight);
673 }
674 cones.push(NonnegativeConeT(b.len() - n_b_old));
675 true
676}
677
678fn clarabel_objective_linear_copp2(
680 s: &[f64],
681 weight: f64,
682 alpha: &[f64],
683 beta: &[f64],
684 q_object: &mut [f64],
685) -> bool {
686 if alpha.len() != s.len() || beta.len() != s.len() - 1 {
687 return false;
688 }
689 for (va, q) in alpha.iter().zip(q_object.iter_mut()) {
692 *q += weight * va;
694 }
695 for (s_pair, vb, q_curr) in izip!(s.windows(2), beta.iter(), q_object.iter_mut()) {
696 *q_curr -= 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
698 }
699 for (s_pair, vb, q_next) in izip!(s.windows(2), beta.iter(), q_object.iter_mut().skip(1)) {
700 *q_next += 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
702 }
703 true
704}
705
706fn clarable_objective_copp2<M: RobotTorque>(
707 problem: &Copp2Problem<M>,
708 objective_constraints: ObjConsClarabel,
709) -> Result<(), CoppError> {
710 let (row, col, val, b, cones, q_object) = objective_constraints;
711 let s = problem
712 .robot
713 .constraints
714 .s_vec(problem.idx_s_interval.0, problem.idx_s_interval.1 + 1)?;
715 let coeffs_torque = if problem.objectives.iter().any(|obj| {
716 matches!(
717 obj,
718 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
719 )
720 }) {
721 problem.robot.torque2_coeff_a(
723 problem.idx_s_interval.0,
724 problem.idx_s_interval.1 - problem.idx_s_interval.0,
725 )?
726 } else {
727 (
728 DMatrix::<f64>::zeros(0, 0),
729 DMatrix::<f64>::zeros(0, 0),
730 DMatrix::<f64>::zeros(0, 0),
731 )
732 };
733 for obj in problem.objectives {
734 match obj {
735 CoppObjective::Time(weight) => {
736 if !clarabel_objective_time_copp2(&s, *weight, (row, col, val, b, cones, q_object))
737 {
738 return Err(CoppError::InvalidInput(
739 "copp2_socp".into(),
740 "Invalid Time objective.".into(),
741 ));
742 }
743 }
744 CoppObjective::ThermalEnergy(weight, normalize) => {
745 if !clarabel_objective_thermal_energy_copp2(
746 &s,
747 *weight,
748 normalize,
749 &coeffs_torque,
750 (row, col, val, b, cones, q_object),
751 ) {
752 return Err(CoppError::InvalidInput(
753 "copp2_socp".into(),
754 "Invalid ThermalEnergy objective.".into(),
755 ));
756 }
757 }
758 CoppObjective::TotalVariationTorque(weight, normalize) => {
759 if !clarabel_objective_tv_torque_copp2(
760 *weight,
761 normalize,
762 &coeffs_torque,
763 (row, col, val, b, cones, q_object),
764 ) {
765 return Err(CoppError::InvalidInput(
766 "copp2_socp".into(),
767 "Invalid TotalVariationTorque objective.".into(),
768 ));
769 }
770 }
771 CoppObjective::Linear(weight, alpha, beta) => {
772 if !clarabel_objective_linear_copp2(&s, *weight, alpha, beta, q_object) {
773 return Err(CoppError::InvalidInput(
774 "copp2_socp".into(),
775 "Invalid Linear objective.".into(),
776 ));
777 }
778 }
779 }
780 }
781 Ok(())
782}
783
784pub(crate) fn objective_value_copp2_opt<M: RobotTorque>(
786 robot: &Robot<M>,
787 start_idx_s: usize,
788 objective: &[CoppObjective],
789 a_profile: &[f64],
790) -> (f64, Vec<f64>) {
791 let Ok(s) = robot
792 .constraints
793 .s_vec(start_idx_s, start_idx_s + a_profile.len())
794 else {
795 return (f64::INFINITY, vec![0.0; objective.len()]);
796 };
797 if a_profile.len() != s.len() {
798 return (f64::INFINITY, vec![0.0; objective.len()]);
799 }
800 let Ok(b_profile) = a_to_b_topp2(&s, a_profile) else {
801 return (f64::INFINITY, vec![0.0; objective.len()]);
802 };
803 let torque = if objective.iter().any(|obj| {
804 matches!(
805 obj,
806 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
807 )
808 }) {
809 let torque_result =
810 robot.get_torque_with_ab(&a_profile[0..a_profile.len() - 1], &b_profile, start_idx_s);
811 match torque_result {
812 Ok(torque) => torque,
813 _ => return (f64::INFINITY, vec![0.0; objective.len()]),
814 }
815 } else {
816 DMatrix::<f64>::zeros(0, 0)
817 };
818 let a_sqrt = if objective.iter().any(|obj| {
819 matches!(
820 obj,
821 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
822 )
823 }) {
824 a_profile.iter().map(|a| a.sqrt()).collect()
825 } else {
826 Vec::new()
827 };
828 let mut obj_val = Vec::with_capacity(objective.len());
829 let mut obj_val_total = 0.0;
830 for obj in objective {
831 match obj {
832 CoppObjective::Time(weight) => {
833 let obj_here = objective_value_time_copp2(&s, &a_sqrt);
834 obj_val.push(obj_here);
835 obj_val_total += weight * obj_here;
836 }
837 CoppObjective::ThermalEnergy(weight, normalize) => {
838 let obj_here =
839 objective_value_thermal_energy_copp2(&s, &a_sqrt, &torque, normalize);
840 obj_val.push(obj_here);
841 obj_val_total += weight * obj_here;
842 }
843 CoppObjective::TotalVariationTorque(weight, normalize) => {
844 let obj_here = objective_value_tv_torque_copp2(&torque, normalize);
845 obj_val.push(obj_here);
846 obj_val_total += weight * obj_here;
847 }
848 CoppObjective::Linear(weight, alpha, beta) => {
849 let obj_here = objective_value_linear_copp2(&s, a_profile, alpha, beta);
850 obj_val.push(obj_here);
851 obj_val_total += weight * obj_here;
852 }
853 }
854 }
855 (obj_val_total, obj_val)
856}
857
858#[inline(always)]
861fn objective_value_time_copp2(s: &[f64], a_sqrt: &[f64]) -> f64 {
862 let mut objective = 0.0;
864 for (s_pair, a_sqrt_pair) in s.windows(2).zip(a_sqrt.windows(2)) {
865 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]);
866 }
867 2.0 * objective
868}
869
870#[inline(always)]
872fn objective_value_thermal_energy_copp2(
873 s: &[f64],
874 a_sqrt: &[f64],
875 torque: &DMatrix<f64>,
876 normalize: &[f64],
877) -> f64 {
878 let mut objective = 0.0;
880 for (s_pair, a_sqrt_pair, torque_col) in
881 izip!(s.windows(2), a_sqrt.windows(2), torque.column_iter())
882 {
883 let mut sum = 0.0;
884 for (torque, normal) in torque_col.iter().zip(normalize.iter()) {
885 sum += (torque * normal).powi(2);
886 }
887 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]) * sum;
888 }
889 2.0 * objective
890}
891
892#[inline(always)]
894fn objective_value_tv_torque_copp2(torque: &DMatrix<f64>, normalize: &[f64]) -> f64 {
895 let mut objective = 0.0;
897 for (torque_col_curr, torque_col_next) in torque.column_iter().tuple_windows() {
898 for (torque_prev, torque_next, normal) in izip!(
899 torque_col_curr.iter(),
900 torque_col_next.iter(),
901 normalize.iter()
902 ) {
903 objective += (torque_next - torque_prev).abs() * normal;
904 }
905 }
906 objective
907}
908
909#[inline(always)]
911fn objective_value_linear_copp2(s: &[f64], a_profile: &[f64], alpha: &[f64], beta: &[f64]) -> f64 {
912 let mut objective = 0.0;
915 for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
916 objective += a_curr * alpha_curr;
918 }
919 for (a_pair, s_pair, beta_curr) in izip!(a_profile.windows(2), s.windows(2), beta.iter()) {
920 objective += 0.5 * beta_curr * (a_pair[1] - a_pair[0]) / (s_pair[1] - s_pair[0]);
922 }
923 objective
924}
925
926#[cfg(test)]
927mod tests {
928 use super::*;
929 use crate::copp::copp2::stable::basic::{
930 Copp2ProblemBuilder, Topp2ProblemBuilder, s_to_t_topp2,
931 };
932 use crate::copp::copp2::stable::reach_set2::{ReachSet2Options, ReachSet2OptionsBuilder};
933 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
934 use crate::copp::{ClarabelOptions, ClarabelOptionsBuilder};
935 use crate::path::{
936 Path, SplineConfig, add_symmetric_axial_limits_for_test, lissajous_path_for_test,
937 };
938 use crate::robot::demo::Plannar2LinkEnd;
939 use crate::robot::robot_core::Robot;
940 use core::panic;
941 use nalgebra::DMatrix;
942 use std::time::Instant;
943 use std::vec;
944
945 #[test]
946 fn test_copp2_socp_only_time() -> Result<(), CoppError> {
947 run_test_copp2_socp_only_time_repeated(1, false)
948 }
949
950 #[test]
953 #[ignore = "slow"]
954 fn test_copp2_socp_only_time_robust() -> Result<(), CoppError> {
955 run_test_copp2_socp_only_time_repeated(100, true)?;
956 Ok(())
957 }
958
959 #[test]
960 fn test_copp2_socp() -> Result<(), CoppError> {
961 let options_socp = ClarabelOptionsBuilder::new()
962 .allow_almost_solved(true)
963 .build()?;
964 run_test_copp2_socp_once(&options_socp)
965 }
966
967 #[test]
968 #[ignore = "bindings"]
969 fn test_copp2_socp_bindings_parity() -> Result<(), CoppError> {
970 let dim = 3;
971 let num_waypoints = 8;
972 let n: usize = 81;
973 let pi = std::f64::consts::PI;
974
975 let waypoints = DMatrix::<f64>::from_fn(dim, num_waypoints, |axis, j| {
976 let s = j as f64 / (num_waypoints - 1) as f64;
977 match axis {
978 0 => 0.20 * (2.0 * pi * s).sin(),
979 1 => 0.15 * (1.5 * pi * s).cos(),
980 2 => 0.10 * s * (1.0 - s),
981 _ => unreachable!("dimension is fixed to 3"),
982 }
983 });
984 let path = Path::from_waypoints(&waypoints, SplineConfig::default())?;
985 let s = DMatrix::<f64>::from_fn(1, n, |_, j| j as f64 / (n - 1) as f64);
986
987 let mut robot = Robot::with_capacity(dim, n);
988 robot
989 .with_s(&s.as_view())?
990 .with_q_from_path_2nd(&path, 0, n)?;
991 add_symmetric_axial_limits_for_test(&mut robot, 10.0, 50.0, None)?;
992 let torque_max = vec![1.0e6; dim];
993 let torque_min = vec![-1.0e6; dim];
994 robot.with_axial_torque((torque_max.as_slice(), n), (torque_min.as_slice(), n), 0)?;
995
996 let normalize = vec![1.0; dim];
997 let options = ClarabelOptionsBuilder::new()
998 .allow_almost_solved(true)
999 .build()?;
1000
1001 let objectives_thermal = [
1002 CoppObjective::Time(1.0),
1003 CoppObjective::ThermalEnergy(1.0, &normalize),
1004 ];
1005 let problem_thermal =
1006 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives_thermal)
1007 .build()?;
1008 let a_thermal = copp2_socp(&problem_thermal, &options)?;
1009 let (t_final_thermal, _) = s_to_t_topp2(s.as_slice(), &a_thermal, 0.0)?;
1010
1011 let objectives_tv = [
1012 CoppObjective::Time(1.0),
1013 CoppObjective::TotalVariationTorque(1.0, &normalize),
1014 ];
1015 let problem_tv =
1016 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &objectives_tv).build()?;
1017 let a_tv = copp2_socp(&problem_tv, &options)?;
1018
1019 crate::verbosity_log!(
1020 crate::diag::Verbosity::Summary,
1021 "COPP2-SOCP Rust bindings parity test: t_final_thermal={:.17}, thermal.len={}, tv.len={}",
1022 t_final_thermal,
1023 a_thermal.len(),
1024 a_tv.len()
1025 );
1026
1027 Ok(())
1028 }
1029
1030 #[test]
1038 #[ignore = "slow"]
1039 fn test_copp2_socp_robust() -> Result<(), CoppError> {
1040 run_test_copp2_socp_repeated(100, true)?;
1041 Ok(())
1042 }
1043
1044 fn run_test_copp2_socp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1045 let options_socp = ClarabelOptionsBuilder::new()
1046 .allow_almost_solved(true)
1047 .build()?;
1048
1049 let mut tc_sum_case0 = 0.0;
1050 let mut tc_sum_case1 = 0.0;
1051 let mut tc_sum_case2 = 0.0;
1052 let mut tc_sum_case3 = 0.0;
1053 let mut tc_sum_case4 = 0.0;
1054 let mut obj_sum_case0 = vec![0.0; 4];
1055 let mut obj_sum_case1 = vec![0.0; 4];
1056 let mut obj_sum_case2 = vec![0.0; 4];
1057 let mut obj_sum_case3 = vec![0.0; 4];
1058 let mut obj_sum_case4 = vec![0.0; 4];
1059
1060 for i_exp in 0..n_exp {
1061 let n: usize = 1000;
1062 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1063 let dim = robot.dim();
1064
1065 let mut rng = rand::rng();
1066 let (s, path, omega, phi) =
1067 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1068 robot
1069 .with_s(&s.as_view())?
1070 .with_q_from_path_2nd(&path, 0, n)?;
1071 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1072
1073 let objectives_test = [
1075 CoppObjective::Time(1.0),
1076 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1077 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1078 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1079 ];
1080 let a_feasible = vec![0.0; n];
1081
1082 let mut copp2_problem = Copp2ProblemBuilder::new(
1084 &robot,
1085 (0, n - 1),
1086 (0.0, 0.0),
1087 &[CoppObjective::Time(1.0)],
1088 )
1089 .build()?;
1090 let start = Instant::now();
1091 let mut a_case0 = copp2_socp(&copp2_problem, &options_socp)?;
1092 let tc_copp2_case0 = start.elapsed().as_secs_f64() * 1E3;
1093 robot
1094 .constraints
1095 .project_to_feasible_topp2(&mut a_case0, &a_feasible, 0)?;
1096 let (_, obj_case0) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case0);
1097
1098 let obj_case1 = [
1100 CoppObjective::Time(1.0),
1101 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1102 ];
1103 copp2_problem.objectives = &obj_case1;
1104 let start = Instant::now();
1105 let mut a_case1 = copp2_socp(&copp2_problem, &options_socp)?;
1106 let tc_copp2_case1 = start.elapsed().as_secs_f64() * 1E3;
1107 robot
1108 .constraints
1109 .project_to_feasible_topp2(&mut a_case1, &a_feasible, 0)?;
1110 let (_, obj_case1) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case1);
1111 if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1112 let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0)?;
1113 let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0)?;
1114 crate::verbosity_log!(
1115 crate::diag::Verbosity::Summary,
1116 "omega = {omega:?}\nphi = {phi:?}"
1117 );
1118 crate::verbosity_log!(
1119 crate::diag::Verbosity::Summary,
1120 "Case 0: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1121 obj_case0[0],
1122 obj_case0[1],
1123 tf_case0
1124 );
1125 crate::verbosity_log!(
1126 crate::diag::Verbosity::Summary,
1127 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1128 obj_case1[0],
1129 obj_case1[1],
1130 tf_case1
1131 );
1132 crate::verbosity_log!(
1133 crate::diag::Verbosity::Summary,
1134 "Interesting... Cases 0 and 1"
1135 );
1136 }
1137
1138 let obj_case2 = [
1140 CoppObjective::Time(1.0),
1141 CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1142 ];
1143 copp2_problem.objectives = &obj_case2;
1144 let start = Instant::now();
1145 let mut a_case2 = copp2_socp(&copp2_problem, &options_socp)?;
1146 let tc_copp2_case2 = start.elapsed().as_secs_f64() * 1E3;
1147 robot
1148 .constraints
1149 .project_to_feasible_topp2(&mut a_case2, &a_feasible, 0)?;
1150 let (_, obj_case2) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case2);
1151 if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1152 crate::verbosity_log!(
1153 crate::diag::Verbosity::Summary,
1154 "omega = {omega:?}\nphi = {phi:?}"
1155 );
1156 crate::verbosity_log!(
1157 crate::diag::Verbosity::Summary,
1158 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1159 obj_case1[0],
1160 obj_case1[1]
1161 );
1162 crate::verbosity_log!(
1163 crate::diag::Verbosity::Summary,
1164 "Case 2: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1165 obj_case2[0],
1166 obj_case2[1]
1167 );
1168 crate::verbosity_log!(
1169 crate::diag::Verbosity::Summary,
1170 "Interesting... Cases 1 and 2"
1171 );
1172 }
1173
1174 let obj_case3 = [
1176 CoppObjective::Time(1.0),
1177 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1178 ];
1179 copp2_problem.objectives = &obj_case3;
1180 let start = Instant::now();
1181 let mut a_case3 = copp2_socp(&copp2_problem, &options_socp)?;
1182 let tc_copp2_case3 = start.elapsed().as_secs_f64() * 1E3;
1183 robot
1184 .constraints
1185 .project_to_feasible_topp2(&mut a_case3, &a_feasible, 0)?;
1186 let (_, obj_case3) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case3);
1187 if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1188 crate::verbosity_log!(
1189 crate::diag::Verbosity::Summary,
1190 "omega = {omega:?}\nphi = {phi:?}"
1191 );
1192 crate::verbosity_log!(
1193 crate::diag::Verbosity::Summary,
1194 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1195 obj_case1[0],
1196 obj_case1[1],
1197 obj_case1[2]
1198 );
1199 crate::verbosity_log!(
1200 crate::diag::Verbosity::Summary,
1201 "Case 3: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1202 obj_case3[0],
1203 obj_case3[1],
1204 obj_case3[2]
1205 );
1206 crate::verbosity_log!(
1207 crate::diag::Verbosity::Summary,
1208 "Interesting... Cases 1 and 3"
1209 );
1210 }
1211
1212 let obj_case4 = [
1214 CoppObjective::Time(1.0),
1215 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1216 ];
1217 copp2_problem.objectives = &obj_case4;
1218 let start = Instant::now();
1219 let mut a_case4 = copp2_socp(&copp2_problem, &options_socp)?;
1220 let tc_copp2_case4 = start.elapsed().as_secs_f64() * 1E3;
1221 robot
1222 .constraints
1223 .project_to_feasible_topp2(&mut a_case4, &a_feasible, 0)?;
1224 let (_, obj_case4) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case4);
1225 if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
1226 crate::verbosity_log!(
1227 crate::diag::Verbosity::Summary,
1228 "omega = {omega:?}\nphi = {phi:?}"
1229 );
1230 crate::verbosity_log!(
1231 crate::diag::Verbosity::Summary,
1232 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1233 obj_case1[0],
1234 obj_case1[1],
1235 obj_case1[3]
1236 );
1237 crate::verbosity_log!(
1238 crate::diag::Verbosity::Summary,
1239 "Case 4: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1240 obj_case4[0],
1241 obj_case4[1],
1242 obj_case4[3]
1243 );
1244 crate::verbosity_log!(
1245 crate::diag::Verbosity::Summary,
1246 "Interesting... Cases 1 and 4"
1247 );
1248 }
1249 if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
1250 crate::verbosity_log!(
1251 crate::diag::Verbosity::Summary,
1252 "omega = {omega:?}\nphi = {phi:?}"
1253 );
1254 crate::verbosity_log!(
1255 crate::diag::Verbosity::Summary,
1256 "Case 2: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1257 obj_case2[0],
1258 obj_case2[2],
1259 obj_case2[3]
1260 );
1261 crate::verbosity_log!(
1262 crate::diag::Verbosity::Summary,
1263 "Case 4: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1264 obj_case4[0],
1265 obj_case4[2],
1266 obj_case4[3]
1267 );
1268 crate::verbosity_log!(
1269 crate::diag::Verbosity::Summary,
1270 "Interesting... Cases 2 and 4"
1271 );
1272 }
1273
1274 if flag_print_step {
1275 crate::verbosity_log!(
1276 crate::diag::Verbosity::Summary,
1277 "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1278 i_exp + 1,
1279 tc_copp2_case0,
1280 obj_case0,
1281 tc_copp2_case1,
1282 obj_case1,
1283 tc_copp2_case2,
1284 obj_case2,
1285 tc_copp2_case3,
1286 obj_case3,
1287 tc_copp2_case4,
1288 obj_case4
1289 );
1290 }
1291
1292 tc_sum_case0 += tc_copp2_case0;
1293 tc_sum_case1 += tc_copp2_case1;
1294 tc_sum_case2 += tc_copp2_case2;
1295 tc_sum_case3 += tc_copp2_case3;
1296 tc_sum_case4 += tc_copp2_case4;
1297 for i in 0..obj_case0.len() {
1298 obj_sum_case0[i] += obj_case0[i];
1299 obj_sum_case1[i] += obj_case1[i];
1300 obj_sum_case2[i] += obj_case2[i];
1301 obj_sum_case3[i] += obj_case3[i];
1302 obj_sum_case4[i] += obj_case4[i];
1303 }
1304 }
1305
1306 for i in 0..4 {
1307 obj_sum_case0[i] /= n_exp as f64;
1308 obj_sum_case1[i] /= n_exp as f64;
1309 obj_sum_case2[i] /= n_exp as f64;
1310 obj_sum_case3[i] /= n_exp as f64;
1311 obj_sum_case4[i] /= n_exp as f64;
1312 }
1313
1314 crate::verbosity_log!(
1315 crate::diag::Verbosity::Summary,
1316 "Average {} experiments:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1317 n_exp,
1318 tc_sum_case0 / n_exp as f64,
1319 obj_sum_case0,
1320 tc_sum_case1 / n_exp as f64,
1321 obj_sum_case1,
1322 tc_sum_case2 / n_exp as f64,
1323 obj_sum_case2,
1324 tc_sum_case3 / n_exp as f64,
1325 obj_sum_case3,
1326 tc_sum_case4 / n_exp as f64,
1327 obj_sum_case4
1328 );
1329
1330 Ok(())
1331 }
1332
1333 fn run_test_copp2_socp_once(_options_socp: &ClarabelOptions) -> Result<(), CoppError> {
1334 run_test_copp2_socp_repeated(1, false)
1335 }
1336
1337 fn run_one_copp2_socp_only_time_case(
1338 options_ra: &ReachSet2Options,
1339 options_socp: &ClarabelOptions,
1340 ) -> Result<(f64, f64, f64, f64, f64, f64), CoppError> {
1341 let n: usize = 1000;
1342 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1343 let dim = robot.dim();
1344
1345 let mut rng = rand::rng();
1346 let (s, path, omega, phi) =
1347 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1348 robot
1349 .with_s(&s.as_view())?
1350 .with_q_from_path_2nd(&path, 0, n)?;
1351 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1352
1353 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1354 let start = Instant::now();
1355 let a_ra = topp2_ra(&topp2_problem, options_ra)?;
1356 let tc_ra = start.elapsed().as_secs_f64() * 1E3;
1357 let (tf_ra, _) = s_to_t_topp2(s.as_slice(), &a_ra, 0.0)?;
1358
1359 let obj1 = [CoppObjective::Linear(
1360 1.0,
1361 &vec![-1.0; n],
1362 &vec![0.0; n - 1],
1363 )];
1364 let mut copp2_problem =
1365 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &obj1).build()?;
1366 let start = Instant::now();
1367 let a_lp = copp2_socp(&copp2_problem, options_socp)?;
1368 let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1369 let (tf_lp, _) = s_to_t_topp2(s.as_slice(), &a_lp, 0.0)?;
1370
1371 copp2_problem.objectives = &[CoppObjective::Time(1.0)];
1372 let start = Instant::now();
1373 let a_qp = copp2_socp(&copp2_problem, options_socp)?;
1374 let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1375 let (tf_qp, _) = s_to_t_topp2(s.as_slice(), &a_qp, 0.0)?;
1376
1377 if (tf_lp - tf_ra).abs() > 1e-3 || (tf_qp - tf_ra).abs() > 1e-3 {
1378 crate::verbosity_log!(
1379 crate::diag::Verbosity::Summary,
1380 "omega = {omega:?}\nphi = {phi:?}"
1381 );
1382 panic!("COPP2 time optimality failed!");
1383 }
1384
1385 Ok((tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp))
1386 }
1387
1388 fn run_test_copp2_socp_only_time_repeated(
1389 n_exp: usize,
1390 flag_print_step: bool,
1391 ) -> Result<(), CoppError> {
1392 let mut tc_sum_ra = 0.0;
1393 let mut tc_sum_lp = 0.0;
1394 let mut tc_sum_qp = 0.0;
1395 let mut tf_sum_ra = 0.0;
1396 let mut tf_sum_lp = 0.0;
1397 let mut tf_sum_qp = 0.0;
1398
1399 let options_ra = ReachSet2OptionsBuilder::new()
1400 .lp_feas_tol(1E-9)
1401 .a_cmp_abs_tol(1E-9)
1402 .a_cmp_rel_tol(1E-9)
1403 .build()?;
1404 let options_socp = ClarabelOptionsBuilder::new()
1405 .allow_almost_solved(true)
1406 .build()?;
1407
1408 for i_exp in 0..n_exp {
1409 let (tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp) =
1410 run_one_copp2_socp_only_time_case(&options_ra, &options_socp)?;
1411
1412 if flag_print_step {
1413 crate::verbosity_log!(
1414 crate::diag::Verbosity::Summary,
1415 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1416 i_exp + 1,
1417 tc_ra,
1418 tc_lp,
1419 tc_qp,
1420 tf_ra,
1421 tf_lp,
1422 tf_qp,
1423 );
1424 }
1425
1426 tc_sum_ra += tc_ra;
1427 tc_sum_lp += tc_lp;
1428 tc_sum_qp += tc_qp;
1429 tf_sum_ra += tf_ra;
1430 tf_sum_lp += tf_lp;
1431 tf_sum_qp += tf_qp;
1432 }
1433
1434 crate::verbosity_log!(
1435 crate::diag::Verbosity::Summary,
1436 "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1437 n_exp,
1438 tc_sum_ra / n_exp as f64,
1439 tc_sum_lp / n_exp as f64,
1440 tc_sum_qp / n_exp as f64,
1441 tf_sum_ra / n_exp as f64,
1442 tf_sum_lp / n_exp as f64,
1443 tf_sum_qp / n_exp as f64
1444 );
1445
1446 Ok(())
1447 }
1448}